package engine;

import java.util.EnumMap;
import java.util.LinkedList;
import java.util.Collection;
import java.util.Map;

/**
 * Statemachine skeleton.
 *
 * <p>You should initialize this once, and then the statemachine ought be
 * powered by {@link Statemachine}, which you create for each of your objects</p>
 *
 * <p>See {@link GraphView} for a way to graphically present the statemachine.</p>
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-12
 */
public class StatemachineModel<ST extends Enum<ST> & State, ET extends Enum<ET> & StateEvent>
{
	/**
	 * Create a new statemachine associated to a given agent.
	 *
	 * <p>Agent may be null. It is useful to provide the agent responsible for
	 * the statemachine, so we may pass you back a pointer to your class on
	 * transitions.</p>
	 *
	 * @param pass your state enum.class
	 * @param pass your event enum.class
	 */
	public StatemachineModel(Class<ST> STclass, Class<ET> ETclass) {
		this.STclass = STclass;
		this.ETclass = ETclass;
		sm = new SM();
		any_sm = new EventTransitions();
	}

	/**
	 * Implement transition for a given event, as specified in
	 * {@link addTransition}.
	 */
	public static interface Transition {
		public void event(Statemachine sm, Object data);
	}

	/**
	 * Adds statemachine line: (state, event, transition, end-state).
	 * @param source state (may be null for any)
	 * @param trigger event
	 * @param the end state
	 * @param transition action to perform (may be null for no action)
	 */
	public void addTransition(ST state, ET event, ST endState, Transition transition) {
		EventTransitions et;
		if(state == null)
			et = any_sm;
		else if(sm.containsKey(state))
			et = sm.get(state);
		else
			sm.put(state, et = new EventTransitions());
		et.put(event, new TransitionEndState(transition, endState));
	}

	//** internal

	private class SM extends EnumMap<ST, EventTransitions> {
		public SM() { super(STclass); }
	}

	public class EventTransitions extends EnumMap<ET, TransitionEndState> {
		public EventTransitions() { super(ETclass); }
	}

	private class TransitionEndState {
		public Transition transition;
		public ST endState;
		public TransitionEndState(Transition transition, ST endState) {
			this.transition = transition;
			this.endState = endState;
		}
	}

	public class IssueEvent {
		public ET event;
		public Object eventData;
		public IssueEvent(ET event, Object eventData) {
			this.event = event;
			this.eventData = eventData;
		}
	}

	/**
	 * Internal usage, by {@link Statemachine}.
	 * @param user back reference to statemachine
	 * @param state current state
	 * @param event trigger event
	 * @param eventData event data
	 * @return whether state was changed
	 */
	public boolean trigger(Statemachine user, State state, StateEvent event, Object eventData) {
		boolean ret = false;
		EventTransitions et = (state != null) ? sm.get(state) : any_sm;
		if(et != null) {
			TransitionEndState tes = et.get(event);
			if(tes != null) {
				// we must update here the state before calling the transition
				// function, because some usages depend on this order
				if(tes.endState != null && user.state != tes.endState) {
					user.state = tes.endState;
					ret = true;
				}
				if(tes.transition != null)
					tes.transition.event(user, eventData);
			}
		}
		return ret;
	}

	/**
	 * For introspection tools to draw the statemachine.
	 * @return the events for the various states
	 */
	public Collection<Tuple> getEntries() {
		LinkedList<Tuple> l = new LinkedList<Tuple>();
		for(Map.Entry<ST, EventTransitions> e1: sm.entrySet())
			for(Map.Entry<ET, TransitionEndState> e2: e1.getValue().entrySet())
				l.add(new Tuple(e1.getKey(), e2.getValue().endState, e2.getKey()));
		for(Map.Entry<ET, TransitionEndState> e: any_sm.entrySet())
			l.add(new Tuple(null, e.getValue().endState, e.getKey()));
		return l;
	}

	public static class Tuple {
		public State src, dst;
		public StateEvent event;
		public Tuple(State src, State dst, StateEvent event) {
			this.src = src;
			this.dst = dst;
			this.event = event;
		}
	}

	private final SM sm;
	private final EventTransitions any_sm;
	private final Class<ST> STclass;
	private final Class<ET> ETclass;
}
